package com.jiangyg.mall.authz.support.authentication.admin;

import com.jiangyg.mall.authz.constant.SecurityConstant;
import com.jiangyg.mall.authz.UserInfo;
import com.jiangyg.mall.authz.dto.AdminLoginParams;
import com.jiangyg.mall.authz.service.AdminAuthenticationService;
import com.jiangyg.mall.authz.service.CaptchaService;
import com.jiangyg.mall.authz.support.AuthzUtils;
import com.jiangyg.mall.authz.support.authentication.exception.CaptchaException;
import com.jiangyg.mall.authz.support.authentication.exception.UnknowException;
import com.jiangyg.mall.authz.support.TokenUtils;
import com.jiangyg.mall.core.support.restful.Result;
import com.jiangyg.mall.core.utils.JsonUtils;
import com.jiangyg.mall.core.utils.Logger;
import com.jiangyg.mall.core.utils.UUIDUtils;
import com.jiangyg.mall.core.utils.WebUtils;
import com.jiangyg.mall.core.utils.bean.BeanUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * 类描述：登录认证
 *
 * @author jiangyg
 * @version 4.0
 * @date 2021-10-09
 */
@Slf4j
public class AdminLoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    /**
     * 验证码
     */
    private final CaptchaService captchaService;

    /**
     * 验证管理器
     */
    private final AuthenticationManager authenticationManager;

    /**
     * 后台管理用户信息认证实现
     */
    private final AdminAuthenticationService adminAuthenticationService;

    public AdminLoginAuthenticationFilter(CaptchaService captchaService,
                                          AuthenticationManager authenticationManager,
                                          AdminAuthenticationService adminAuthenticationService) {
        this.setPostOnly(false);
        this.captchaService = captchaService;
        this.authenticationManager = authenticationManager;
        this.adminAuthenticationService = adminAuthenticationService;
        AntPathRequestMatcher matcher = new AntPathRequestMatcher(SecurityConstant.LOGIN_URL);
        this.setRequiresAuthenticationRequestMatcher(matcher);
    }

    /**
     * 功能描述：验证提交的用户信息
     *
     * @param request  请求
     * @param response 响应
     * @return 验证信息
     * @throws AuthenticationException 认证异常
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            // 解析登录信息
            final String body = WebUtils.readBodyToString(request);
            if (StringUtils.isBlank(body)) {
                throw new BadCredentialsException("未在请求体中获取到认证信息！");
            }
            final AdminLoginParams user = JsonUtils.parseObject(body, AdminLoginParams.class);
            // 验证码校验
            final boolean validate = captchaService.validate(user.getUuid(), user.getCaptcha());
            if (!validate) {
                throw new CaptchaException("验证码认证失败！");
            }
            // 账户密码验证
            Logger.info(log, () -> String.format("登录认证：开始进行用户[%s]的登录认证处理", user.getUsername()));
            final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
            return authenticationManager.authenticate(authentication);
        } catch (AuthenticationException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new UnknowException("用户登录认证异常：", ex);
        }
    }

    /**
     * 功能描述：认证成功调用的方法
     *
     * @param request    请求
     * @param response   响应
     * @param chain      过滤器链
     * @param authResult 认证结果
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain, Authentication authResult) {
        try {
            // 1. 获取过期时间（令牌、权限缓存的过期时间）
            final int timeout = SecurityConstant.timeout();
            // 2. 认证成功，得到认证成功之后用户信息
            final AdminUserDetails user = (AdminUserDetails) authResult.getPrincipal();
            // 3. 根据用户信息生成token
            final Map<String, Object> claims = transformClaims(user);
            final String token = TokenUtils.build(claims, SecurityConstant.securityBase64(), timeout);
            // 4. 登录成功之后，
            this.adminAuthenticationService.loadGrantedAuthorities(user.getId());
            Logger.debug(log, () -> String.format("登录认证：用户[%s]认证成功，生成token：%s", user.getUsername(), token));
            // 5. 认证信息返回
            AuthzUtils.writeToken(response, token);
            WebUtils.out(response, Result.ok(claims));
        } catch (Exception ex) {
            Logger.error(log, () -> String.format("用户[%s]登录成功后处理失败", JsonUtils.toJSONString(authResult.getPrincipal())), ex);
        }
    }

    /**
     * 功能描述：转换用户授权信息
     *
     * @param user 登陆用户信息
     * @return 用户授权信息
     * @throws Exception 异常
     */
    private Map<String, Object> transformClaims(AdminUserDetails user) throws Exception {
        UserInfo uinfo = new UserInfo();
        uinfo.setId(user.getId());
        uinfo.setJti(UUIDUtils.build());
        uinfo.setUsername(user.getUsername());
        uinfo.setFullname(user.getUsername());
        uinfo.setMail(user.getMail());
        uinfo.setPhone(user.getPhone());
        return BeanUtils.beanToMap(uinfo);
    }

    /**
     * 功能描述：认证失败处理
     *
     * @param request  请求
     * @param response 响应
     * @param failed   失败
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
        // 账户或者密码错误
        if (failed instanceof BadCredentialsException) {
            WebUtils.out(response, Result.instance("430"));
            return;
        }
        // 验证码异常
        if (failed instanceof CaptchaException) {
            WebUtils.out(response, Result.instance("434"));
            return;
        }
        // 账户过期
        if (failed instanceof AccountExpiredException) {
            WebUtils.out(response, Result.instance("431"));
            return;
        }
        // 账户状态异常
        if (failed instanceof AccountStatusException) {
            WebUtils.out(response, Result.instance("432"));
            return;
        }
        // 账户锁定异常
        if (failed instanceof LockedException) {
            WebUtils.out(response, Result.instance("433"));
            return;
        }
        Logger.error(log, () -> "登录认证异常：", failed);
        WebUtils.out(response, Result.instance("439"));
    }

}
